package model

/*******************************************************************************
Copyright:cloud
Author:cloudapex@126.com
Version:1.0
Date:2020-07-07
Description: 权限模型
*******************************************************************************/
import (
	"encoding/json"
	"fmt"
	"strings"

	"gitee.com/cloudapex/ulern/util"
	"xorm.io/xorm"
)

// => 权限表
type Power struct {
	Id       int64     `json:"id"`
	ParentId int64     `json:"parentid" xorm:"notnull index"`                          // 父Id
	NodePath string    `json:"-" xorm:"varchar(64) notnull"`                           // 节点路径
	CoId     int64     `json:"coId" xorm:"notnull default 0 unique(co_typ_match)"`     // 所属公司ID
	Icon     string    `json:"icon" xorm:"varchar(128)"`                               // 图标
	Name     string    `json:"name" xorm:"varchar(64) notnull"`                        // 权限名称
	Type     EPower    `json:"type" xorm:"notnull default 1 unique(co_typ_match)"`     // 菜单类型(1:菜单;2:功能;3:数据)
	Match    string    `json:"match" xorm:"varchar(128) notnull unique(co_typ_match)"` // 权限匹配项
	Comp     string    `json:"comp,omitempty" xorm:"varchar(64)"`                      // 组件[菜单类型]
	UrlPath  string    `json:"urlPath,omitempty" xorm:"varchar(128)"`                  // 地址栏路径[菜单类型]
	Tntyp    ETreeNode `json:"tntyp" xorm:"notnull default 2"`                         // 用户组类型(1:子节点;2:叶子节点)
	Order    int32     `json:"order" xorm:"notnull default 0 index"`                   // 排序值
	State    int32     `json:"state" xorm:"notnull default 1"`                         // 状态(1:正常;2:失效)
	Remark   string    `json:"remark" xorm:"varchar(128)"`                             // 备注

	CreatedAt util.TJsTime `json:"at" xorm:"created"`
	UpdatedAt util.TJsTime `json:"-" xorm:"updated"`

	CInIds  []int64 `json:"-" xorm:"-"`
	CInPIds []int64 `json:"-" xorm:"-"`
	CMatch  string  `json:"-" xorm:"-"`
	Childs  []Power `json:"children" xorm:"-"`
}

// => 菜单树(标签)
type MenuLable struct {
	Id     int64       `json:"id"`
	Name   string      `json:"name"` // 权限名
	Childs []MenuLable `json:"children,omitempty"`
}

func (this *Power) TableName() string { return "power" }

func (this *Power) Load() (bool, error) {
	return MDB(EMDB_Permt).Get(this)
}
func (this *Power) Create() (int64, error) {
	return MDB(EMDB_Permt).Insert(this)
}
func (this *Power) Update(id int64, columns ...string) (int64, error) {
	return MDB(EMDB_Permt).ID(id).MustCols(columns...).Update(this)
}
func (this *Power) Delete(id int64) (int64, error) {
	return MDB(EMDB_Permt).ID(id).Delete(&Power{})
}
func (this *Power) condis(q *xorm.Session, tbl string) *xorm.Session {
	util.Cast(tbl != "", func() { tbl += "." }, nil)

	if this.CoId != 0 {
		q = q.Where(fmt.Sprintf("%sco_id = ?", tbl), this.CoId)
	}
	if this.Name != "" {
		q = q.Where(fmt.Sprintf("%sname like ?", tbl), "%"+this.Name+"%")
	}
	if this.Match != "" {
		q = q.Where(fmt.Sprintf("%s`match` = ?", tbl), this.Match)
	}
	if this.Type > 0 {
		q = q.Where(fmt.Sprintf("%stype = ?", tbl), this.Type)
	}
	if this.State > 0 {
		q = q.Where(fmt.Sprintf("%sstate = ?", tbl), this.State)
	}
	if this.CMatch != "" {
		q = q.Where(fmt.Sprintf("%s`match` like ?", tbl), "%"+this.CMatch+"%")
	}
	if len(this.CInIds) > 0 {
		q = q.In(fmt.Sprintf("%sid", tbl), this.CInIds)
	}
	if len(this.CInPIds) > 0 {
		q = q.In(fmt.Sprintf("%sparent_id", tbl), this.CInPIds)
	}
	return q
}
func (this *Power) condList() bool {
	return this.Name != "" || this.Match != "" || this.CMatch != "" || this.Type != 0 || len(this.CInIds) > 0
}
func (this *Power) Find() ([]Power, error) {
	sl, dl := []*Power{}, []Power{}

	q := MDB(EMDB_Permt).Table(&Power{})
	q = this.condis(q, "")

	// 按普通列表返回
	if this.condList() {
		if err := q.Asc("order").Find(&dl); err != nil {
			return nil, err
		}
		return dl, nil
	}

	// 按树形列表返回
	if err := q.Asc("order").Find(&sl); err != nil {
		return nil, err
	}

	for _, it := range sl {
		if it.ParentId != 0 {
			continue
		}
		dl = append(dl, nestMenu(sl, *it))
	}
	return dl, nil
}
func (this *Power) Findm() (map[int64]*Power, error) {
	dl := map[int64]*Power{}

	q := MDB(EMDB_Permt).Table(&Power{})
	q = this.condis(q, "")

	if err := q.Asc("order").Find(&dl); err != nil {
		return nil, err
	}
	return dl, nil
}
func (this *Power) FindTree() ([]MenuLable, error) {
	sl := []*Power{}

	q := MDB(EMDB_Permt).Table(&Power{})
	q = this.condis(q, "")
	if err := q.Asc("order").Find(&sl); err != nil {
		return nil, err
	}

	dl := []MenuLable{}
	for _, it := range sl {
		if it.ParentId != 0 {
			continue
		}
		dl = append(dl, nestMenuLable(sl, MenuLable{it.Id, it.Name, nil}))
	}

	return dl, nil
}
func (this *Power) VerifyIds(ids []int64) (bool, error) {
	if len(ids) == 0 {
		return true, nil
	}
	c, err := MDB(EMDB_Permt).Table(&Power{}).In("id", ids).Count()
	return c == int64(len(ids)), err
}

// ----- RolePowerWithPower

// => 菜单树(匹配)
type MenuItem struct {
	Id     int64      `json:"-"`
	Name   string     `json:"name"`
	Type   EPower     `json:"type"`
	Icon   string     `json:"icon"`
	Match  string     `json:"match"`          // 权限匹配项
	Comp   string     `json:"comp,omitempty"` // 组件[菜单类型]
	Path   string     `json:"path,omitempty"` // 地址栏路径[菜单类型]
	Childs []MenuItem `json:"children,omitempty"`
}

// => 权限列表(标签)
type PowerItem struct {
	Id    int64  `json:"id" xorm:"id"`
	Match string `json:"match" xorm:"match"` // 权限匹配项
}

// => 权限列表(全链)
type PowerChain struct {
	Id      int64   `json:"id"`
	LName   string  `json:"lName,omitempty"` // 链名称
	PathIds []int64 `json:"-"`               // 路径上的Ids
}

// => 数据权限(角色)
type RoleScope struct {
	RId   int64  `xorm:"role_id"`
	Scope string `xorm:"match"`
}

type RolePowerWithPower struct {
	RolePower `xorm:"extends"`
	Power     `xorm:"extends"`
}

func (this *RolePowerWithPower) FindMItems(e ...EPower) ([]MenuItem, error) {
	sl, dl := []*Power{}, []MenuItem{}
	tblPower := MDB(EMDB_Permt).TableName(&this.Power)

	q := MDB(EMDB_Permt).Table(&RolePower{}).Select(fmt.Sprintf("`%s`.*", tblPower))
	q = q.Join("INNER", tblPower, fmt.Sprintf("role_power.power_id = %s.id", tblPower))
	q = this.RolePower.condis(q, "role_power")
	q = this.Power.condis(q, tblPower)
	if err := q.In(tblPower+".type", e).GroupBy(tblPower + ".id").Asc(tblPower + ".order").Find(&sl); err != nil {
		return nil, err
	}

	for _, it := range sl {
		if it.ParentId != 0 {
			continue
		}
		dl = append(dl, nestMenuItem(sl, MenuItem{it.Id, it.Name, it.Type, it.Icon, it.Match, it.Comp, it.UrlPath, nil}))
	}

	return dl, nil
}
func (this *RolePowerWithPower) FindPItems(e ...EPower) ([]PowerItem, error) {
	dl := []PowerItem{}
	tblPower := MDB(EMDB_Permt).TableName(&this.Power)

	q := MDB(EMDB_Permt).Table(&RolePower{}).Cols(tblPower+".id", tblPower+".match")
	q = q.Join("INNER", tblPower, fmt.Sprintf("role_power.power_id = %s.id", tblPower))
	q = this.RolePower.condis(q, "role_power")
	if err := q.In(tblPower+".type", e).GroupBy(tblPower+".id").Asc(tblPower+".parent_id", tblPower+".order").Find(&dl); err != nil {
		return dl, err
	}
	return dl, nil
}
func (this *RolePowerWithPower) FindScopes() ([]RoleScope, error) {
	dl := []RoleScope{}
	tblPower := MDB(EMDB_Permt).TableName(&this.Power)

	q := MDB(EMDB_Permt).Table(&RolePower{}).Cols("role_power.role_id", tblPower+".match")
	q = q.Join("INNER", tblPower, fmt.Sprintf("role_power.power_id = %s.id", tblPower))
	q = this.RolePower.condis(q, "role_power")
	q = this.Power.condis(q, tblPower)
	if err := q.Asc(tblPower + ".order").Find(&dl); err != nil {
		return nil, err
	}
	return dl, nil
}
func (this *RolePowerWithPower) FindChains(e ...EPower) ([]PowerChain, error) {
	sl, dl := []*Power{}, []PowerChain{}
	tblPower := MDB(EMDB_Permt).TableName(&this.Power)

	q := MDB(EMDB_Permt).Table(&RolePower{}).Select(fmt.Sprintf("`%s`.*", tblPower))
	q = q.Join("INNER", tblPower, fmt.Sprintf("role_power.power_id = %s.id", tblPower))
	q = this.RolePower.condis(q, "role_power")
	if err := q.In(tblPower+".type", e).GroupBy(tblPower+".id").Asc(tblPower+".parent_id", tblPower+".order").Find(&sl); err != nil {
		return dl, err
	}

	aIds := []int64{}
	for n, it := range sl {
		dl = append(dl, PowerChain{Id: it.Id, LName: it.Name})
		json.Unmarshal([]byte("["+strings.Join(strings.Split(it.NodePath, "/"), ",")+"]"), &dl[n].PathIds)
		aIds = append(aIds, dl[n].PathIds...)
	}

	aIds = util.RemoveDuplicate64(aIds)
	powers, err := (&Power{CInIds: aIds}).Findm()
	if err != nil {
		return dl, err
	}

	for i, _ := range dl {
		names := []string{}
		for _, id := range dl[i].PathIds {
			if it, ok := powers[id]; ok {
				names = append(names, it.Name)
			}
		}
		names = append(names, dl[i].LName)
		dl[i].LName = strings.Join(names, "-")
	}
	return dl, nil
}

//------------------------------------------------------------------------------

func nestMenu(sl []*Power, menu Power) Power {
	for _, it := range sl {
		if menu.Id != it.ParentId {
			continue
		}

		if it.Tntyp == ETreeNode_Leaf {
			menu.Childs = append(menu.Childs, *it)
		} else {
			menu.Childs = append(menu.Childs, nestMenu(sl, *it))
		}
	}
	return menu
}
func nestMenuLable(sl []*Power, menu MenuLable) MenuLable {
	for _, it := range sl {
		if menu.Id != it.ParentId {
			continue
		}

		if it.Tntyp == ETreeNode_Leaf {
			menu.Childs = append(menu.Childs, MenuLable{it.Id, it.Name, nil})
		} else {
			menu.Childs = append(menu.Childs, nestMenuLable(sl, MenuLable{it.Id, it.Name, nil}))
		}
	}
	return menu
}
func nestMenuItem(sl []*Power, menu MenuItem) MenuItem {
	for _, it := range sl {
		if menu.Id != it.ParentId {
			continue
		}

		if it.Tntyp == ETreeNode_Leaf {
			menu.Childs = append(menu.Childs, MenuItem{it.Id, it.Name, it.Type, it.Icon, it.Match, it.Comp, it.UrlPath, nil})
		} else {
			menu.Childs = append(menu.Childs, nestMenuItem(sl, MenuItem{it.Id, it.Name, it.Type, it.Icon, it.Match, it.Comp, it.UrlPath, nil}))
		}
	}
	return menu
}
